﻿/*            Copyright(c) 2006-2008 Dave Sexton           *
 * http://www.codeplex.com/DocProject                      *
 * http://www.codeplex.com/DocProject/Project/License.aspx *
 ***********************************************************/
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Text;
using System.Windows.Forms;

namespace DaveSexton.DocProject.Sandcastle.TopicManagement
{
	internal sealed partial class TopicPropertiesControl : UserControl
	{
		#region Public Properties
		[Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
		public TopicManager Manager
		{
			get
			{
				return manager;
			}
			set
			{
				if (manager == value)
					return;

				UpdateManagerEventHandlers(false);

				manager = value;

				UpdateManagerEventHandlers(true);

				if (topic != null)
				{
					EnsureChangesCommitted();

					topic = null;

					UpdateForTopic();
				}

				if (manager != null)
					Enabled = true;
				else
					Enabled = false;
			}
		}

		[Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
		public Topic Topic
		{
			set
			{
				if (topic != value)
				{
					topic = value;
					metadata = null;

					UpdateForTopic();
				}
			}
		}

		[Category("Appearance"), Description("Specifies whether the active topic's title is displayed in the UI.")]
		public bool TopicLabelVisible
		{
			get
			{
				return topicLabelVisible;
			}
			set
			{
				topicLabelVisible = value;
				topicLabel.Visible = value;
			}
		}
		#endregion

		#region Private / Protected
		private Topic topic;
		private TopicMetadata metadata;
		private TopicManager manager;
		private bool topicLabelVisible = true;
		private DataSet keywordsDataSet;
		#endregion

		#region Constructors
		/// <summary>
		/// Constructs a new instance of the <see cref="TopicPropertiesControl" /> class.
		/// </summary>
		public TopicPropertiesControl()
		{
			InitializeComponent();

			keywordsDataGridView.AutoGenerateColumns = false;
			nestedKeywordsDataGridView.AutoGenerateColumns = false;
		}
		#endregion

		#region Methods
		private void UpdateManagerEventHandlers(bool bind)
		{
			if (manager != null)
			{
				if (bind)
				{
					manager.Saving += manager_Saving;
					manager.Saved += manager_Saved;
				}
				else
				{
					manager.Saving -= manager_Saving;
					manager.Saved -= manager_Saved;
				}
			}
		}

		/// <remarks>
		/// A bug exists in either VS or the .NET framework that causes the TreeView.AfterSelect event to be raised without it getting the input focus first, 
		/// but only if it was clicked while a TextBox currently has the input focus; i.e., while editing a text box on this control, if you click 
		/// the topic explorer's tree view then your changes will be lost since AfterSelect will be raised immediately, causing this control's active topic to be 
		/// changed to the newly selected topic, and then afterwards the source textbox's Validating event will be raised, once it finally loses focus, and the 
		/// changes you have made to the previously selected topic.  This is verifyable by examining the order of events in the Debug output.
		/// The order of events is normally the other way around, although I couldn't reproduce this issue in a smaller test app and it only appears to happen
		/// when the Topic Editor (source mode is a TextBox control) and Topic Explorer controls are being hosted by different tool windows (e.g., the Topic 
		/// Management dialog actually behaves correctly and it hosts both controls in the same Form.)
		/// <para>
		/// Apparently a related bug was reported in the .NET 1.1 Framework (http://support.microsoft.com/kb/824996) and was fixed in a service pack.
		/// </para>
		/// The solution is to call <see cref="ValidateChildren"/> explicitly each time a new topic is assigned since the Framework may not call it on its own.
		/// </remarks>
		private void EnsureChangesCommitted()
		{
			ValidateChildren();
		}

		private void UpdateForTopic()
		{
			EnsureChangesCommitted();

			bool enabled = ShouldEnableForTopic();

			tabs.Enabled = enabled;

			ResetUI();

			if (enabled)
			{
				topicLabel.Text = topic.TocTitle;

				if (topic.TopicType == TopicType.Conceptual)
				{
					metadata = manager.GetMetadata(topic);

					titleTextBox.Enabled = true;
					keywordsSplitContainer.Enabled = true;
					attributesGrid.Enabled = true;

					titleTextBox.DataBindings.Add("Text", metadata, "Title", false, DataSourceUpdateMode.OnValidation);

					keywordsDataSet = Help2xProject.CreateKeywordsDataSet(metadata.Keywords);

					BindingSource keywordsSource = new BindingSource(keywordsDataSet, "Keywords");
					BindingSource nestedKeywordsSource = new BindingSource(keywordsSource, "KeywordsRelation");

					keywordsDataGridView.DataSource = keywordsSource;
					nestedKeywordsDataGridView.DataSource = nestedKeywordsSource;

					attributesGrid.Metadata = metadata.Attributes;
				}
				else
				{
					metadata = null;
					keywordsSplitContainer.Enabled = false;
					attributesGrid.Enabled = false;
					titleTextBox.Enabled = false;
				}
			}
		}

		private void ResetUI()
		{
			topicLabel.Text = string.Empty;

			titleTextBox.DataBindings.Clear();
			titleTextBox.Text = string.Empty;

			attributesGrid.Metadata = null;
			nestedKeywordsDataGridView.DataSource = null;
			keywordsDataGridView.DataSource = null;
		}

		private bool ShouldEnableForTopic()
		{
			bool enable = false;

			if (manager != null)
			{
				TopicType type = (topic == null) ? TopicType.General : topic.TopicType;
				enable = type != TopicType.General && type != TopicType.Module && type != TopicType.Container && type != TopicType.Html;
			}

			return enable;
		}

		private static void HandleDataGridViewError(Exception ex)
		{
			if (ex is ConstraintException)
				DocProjectEnvironment.ShowErrorDialog(ex.Message);
		}

		#region Hack
		// HACK: This event handler is a workaround for an EditOnEnter bug that prevents rows from being selected when a header is clicked.
		// This code is based on the workaround found here: https://connect.microsoft.com/VisualStudio/feedback/Workaround.aspx?FeedbackID=98504
		// The DataGridView's CellEnter and MouseClick events are used to call these methods.
		private static void SetDataGridViewEditOnEnter(DataGridView control)
		{
			control.EditMode = DataGridViewEditMode.EditOnEnter;
		}

		private static void SetDataGridViewEditOnKeystrokeOrF2(DataGridView control, int x, int y)
		{
			DataGridView.HitTestInfo hit = control.HitTest(x, y);

			if (hit.Type == DataGridViewHitTestType.RowHeader)
			{
				control.EditMode = DataGridViewEditMode.EditOnKeystrokeOrF2;
				control.EndEdit();
			}
		}
		#endregion

		/// <summary> 
		/// Clean up any resources being used.
		/// </summary>
		/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
		protected override void Dispose(bool disposing)
		{
			if (disposing)
			{
				UpdateManagerEventHandlers(false);

				if (components != null)
					components.Dispose();
			}
			base.Dispose(disposing);
		}
		#endregion

		#region Event Handlers
		private void manager_Saving(object sender, TopicManagerChangesEventArgs e)
		{
			// When this control is hosted in a tool window, changing the focus 
			// to the IDE does not cause the control to lose focus or validate.
			// To ensure that all changes are known before the manager attempts to save,
			// validation must occur since updates to state occur in validation event
			// handlers.
			EnsureChangesCommitted();
		}

		private void manager_Saved(object sender, TopicManagerChangesEventArgs e)
		{
			if ((e.Changes & TopicManagerChanges.Properties) == TopicManagerChanges.Properties)
				UpdateForTopic();
		}

		private void keywordsDataGridView_DefaultValuesNeeded(object sender, DataGridViewRowEventArgs e)
		{
			e.Row.Cells[typeColumn.Index].Value = Help2xProject.GetIndexTitle("K");
		}

		private void keywordsDataGridView_Validated(object sender, EventArgs e)
		{
			if (metadata != null)
				Help2xProject.FillKeywordsFromDataSet(keywordsDataSet, metadata.Keywords);
		}

		private void nestedKeywordsDataGridView_Validated(object sender, EventArgs e)
		{
			if (metadata != null)
				Help2xProject.FillKeywordsFromDataSet(keywordsDataSet, metadata.Keywords);
		}

		private void keywordsDataGridView_DataError(object sender, DataGridViewDataErrorEventArgs e)
		{
			if (e.Exception != null && e.Context == DataGridViewDataErrorContexts.Commit)
				HandleDataGridViewError(e.Exception);
		}

		private void nestedKeywordsDataGridView_DataError(object sender, DataGridViewDataErrorEventArgs e)
		{
			if (e.Exception != null && e.Context == DataGridViewDataErrorContexts.Commit)
				HandleDataGridViewError(e.Exception);
		}

		private void keywordsDataGridView_CellEnter(object sender, DataGridViewCellEventArgs e)
		{
			SetDataGridViewEditOnEnter(keywordsDataGridView);
		}

		private void keywordsDataGridView_MouseClick(object sender, MouseEventArgs e)
		{
			SetDataGridViewEditOnKeystrokeOrF2(keywordsDataGridView, e.X, e.Y);
		}

		private void nestedKeywordsDataGridView_CellEnter(object sender, DataGridViewCellEventArgs e)
		{
			SetDataGridViewEditOnEnter(nestedKeywordsDataGridView);
		}

		private void nestedKeywordsDataGridView_MouseClick(object sender, MouseEventArgs e)
		{
			SetDataGridViewEditOnKeystrokeOrF2(nestedKeywordsDataGridView, e.X, e.Y);
		}
		#endregion
	}
}
